/**
 * ZHU framework. Copyright 2012-, all rights reserved.
 *
 * $Id: MyUtility.java 33 2012-08-30 08:10:18Z zhuxiaopeng $
 * $Revision: 33 $
 * $Date: 2012-08-30 16:10:18 +0800 (四, 2012-08-30) $
 * $Author: zhuxiaopeng $
 */
package zhu.framework.util;

import java.lang.reflect.*;
import java.util.logging.*;

import javax.inject.*;

/**
 * <p>
 * </p>
 * 
 * @author $Author: zhuxiaopeng $
 * @version $Revision: 33 $ - $Date: 2012-08-30 16:10:18 +0800 (四, 2012-08-30) $
 */
public final class MyUtility {

	/**
	 * <p>
	 * <code>callerDepth</code>によって指定されたMethodの <code>StackTraceElement</code>
	 * を取得する。 パラメータ<code>callerDepth<code>は<code>StackTraceElement</code>
	 * の取得を希望する Methodからこのメソッドまでの呼び出しスタックの深さで、 0から始まり、&quot;<code>&gt;=0</code>
	 * &quot;でなければならない。 Methodの呼び出しスタックが次の通りだと仮定する :
	 * <code>[This Method] <- [Method A] <- [Method B]</code>. この場合、
	 * <code>[Method A]</code>を取得するためには<code>0</code>を、 <code>[Method B]</code>
	 * を取得するためには<code>1</code>指定する。 <code>callerDepth</code>
	 * として現在の呼び出しスタックの深さを超える値が指定された場合、
	 * <code>ArrayIndexOutOfBoundsException</code>が送出される。
	 * </p>
	 * <h3>実装上の注記</h3>
	 * <p>
	 * この実装は、このMethodがこのClass内の他のMethodを呼び出さないと仮定している。
	 * 実装を変更する場合は、スタック探索のアルゴリズムを変更する必要があるかもしれない。
	 * </p>
	 * 
	 * @param callerDepth
	 *            0から始まる希望のMethodからの呼び出し深さ。&quot;<code>&gt;=0</code>
	 *            &quot;である必要がある。
	 * @return 指定されたMethodの<code>StackTraceElement</code>
	 * @throws ArrayIndexOutOfBoundsException
	 *             <code>callerDepth</code>が過大である場合。
	 * @throws IllegalStateException
	 *             このMethod自身が現在のThreadの呼び出しスタック内に存在しない場合。
	 */
	static public StackTraceElement getCallerStackTraceElement(final int callerDepth) throws ArrayIndexOutOfBoundsException, IllegalStateException {
		final String MY_METHOD_NAME = "getCallerStackTraceElement";
		if (callerDepth < 0) {
			final IllegalArgumentException e = new IllegalArgumentException(String.format("Parameter 'callerDepth' should be '>0' but %d",
					callerDepth));
			logger.throwing("zhu.framework.util.zhuUtility", "getCallerStackTraceElement", e);
			throw e;
		}
		// Assumes that this method never invokes any methods in this class.
		final StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
		final int elementCount = stackTraceElements.length;
		final String myClassName = MyUtility.class.getName();
		int myStackElementIndex = 0;
		boolean found = false;
		for (; myStackElementIndex < elementCount; myStackElementIndex++) {
			if (stackTraceElements[myStackElementIndex].getClassName().equals(myClassName)
					&& stackTraceElements[myStackElementIndex].getMethodName().equals(MY_METHOD_NAME)) {
				// Found myself
				found = true;
				break;
			}
		}
		if (found == false) {
			// This method is not in the current thread's stack : it's an
			// unexpected result.
			final IllegalStateException e = new IllegalStateException("My method information is not in current thread's stack");
			logger.throwing("zhu.framework.util.zhuUtility", "getCallerStackTraceElement", e);
			throw e;
		}
		final StackTraceElement theClientStack;
		theClientStack = stackTraceElements[myStackElementIndex + 1 + callerDepth];
		return theClientStack;
	}

	/**
	 * <p>
	 * 指定されたメソッド・パラメータの値が<code>null</code>である場合に
	 * <code>NullPointerException</code>送出する。 それ以外の場合には何もしない。
	 * メソッドパラメータではなく、一般的な条件チェックは
	 * <code>{@link #checkNotNull(Object, String)}</code>を使う。
	 * </p>
	 * <p>
	 * 例外を送出する場合に限って、 呼び出し元のClass名とMethod名はReflection APIを通じて取得する。
	 * </p>
	 * 
	 * @param paramName
	 *            チェック対象のパラメータ名
	 * @param paramValue
	 *            チェック対象のパラメータの値
	 * 
	 * @throws NullPointerException
	 *             <code>paramValue</code>が<code>null</code>の場合
	 */
	static public <T> void checkMethodParameterNotNull(final String paramName, final T paramValue) throws NullPointerException {
		if (paramValue == null) {
			final StackTraceElement callerStack = getCallerStackTraceElement(1);
			final String callerClassName = callerStack.getClassName();
			final String callerMethodName = callerStack.getMethodName();
			final String message = String.format("Parameter %s cannot be null for %s#%s()", paramName, callerClassName, callerMethodName);
			final NullPointerException e = new NullPointerException(message);
			throw e;
		}
	}

	/**
	 * <p>
	 * 指定された値が<code>null</code>である場合に <code>NullPointerException</code>送出する。
	 * それ以外の場合には何もしない。 {@link #checkNotNull(Object, String, Object[])
	 * メッセージ文としてフォーマット付きの文字列を使用できるバージョン}もある。 メソッド・パラメータのチェックには
	 * <code>{@link #checkMethodParameterNotNull(String, Object)}</code>
	 * を使用する方が便利である。
	 * </p>
	 * <p>
	 * <code>message</code>に文字列を指定すると、例外を送出するときにはその内容が例外のコンストラクタに渡される。 このパラメータが
	 * <code>null</code>の場合は、メソッド名等が含まれたデフォルトのメッセージが使用される。
	 * </p>
	 * <p>
	 * 例外を送出する場合に限って、 呼び出し元のClass名とMethod名はReflection APIを通じて取得する。
	 * </p>
	 * 
	 * @param paramValue
	 *            チェック対象のパラメータの値
	 * @param message
	 *            <code>paramValue==null</code>の場合に例外に指定するメッセージ、または
	 *            <code>null</code>
	 * 
	 * @throws NullPointerException
	 *             <code>paramValue</code>が<code>null</code>の場合
	 */
	static public <T> void checkNotNull(final T paramValue, final String message) throws NullPointerException {
		if (paramValue == null) {
			if (message == null) {
				final StackTraceElement callerStack = getCallerStackTraceElement(1);
				final String callerClassName = callerStack.getClassName();
				final String callerMethodName = callerStack.getMethodName();
				final String msg = String.format("Unexpected null in %s#%s() at line #%d in %s", callerClassName, callerMethodName,
						callerStack.getLineNumber(), callerStack.getFileName());
				final NullPointerException e = new NullPointerException(msg);
				throw e;
			} else {
				final NullPointerException e = new NullPointerException(message);
				throw e;
			}
		}
	}

	/**
	 * <p>
	 * 指定された値が<code>null</code>である場合に <code>NullPointerException</code>送出する。
	 * それ以外の場合には何もしない。 メソッド・パラメータのチェックには
	 * <code>{@link #checkMethodParameterNotNull(String, Object)}</code>
	 * を使用する方が便利である。
	 * </p>
	 * <p>
	 * 例外が送出される場合、<code>messageFormat</code>をフォーマットした文字列がメッセージとして渡される。
	 * <code>messageFormat</code>には
	 * <code>{@link String#format(String, Object[])}</code>
	 * で使用する書式文字列を渡し、書式に対する引数を可変引数<code>messageArgs</code>
	 * に渡せば、例外送出時に限ってフォーマットが行われる。
	 * </p>
	 * 
	 * @param paramValue
	 *            チェック対象のパラメータの値
	 * @param messageFormat
	 *            <code>paramValue==null</code>の場合に例外に指定するメッセージのフォーマット
	 * @param messageArgs
	 *            メッセージのフォーマットに渡す引数
	 * 
	 * @throws NullPointerException
	 *             <code>paramValue</code>が<code>null</code>の場合、または
	 *             <code>messageFormat==null</code>の場合
	 */
	static public <T> void checkNotNull(final T paramValue, final String messageFormat, final Object... messageArgs) throws NullPointerException {
		MyUtility.checkMethodParameterNotNull("messageFormat", messageFormat);
		assert (messageArgs != null);
		//
		if (paramValue == null) {
			final NullPointerException e = new NullPointerException(String.format(messageFormat, messageArgs));
			throw e;
		}
	}

	static public void rethrowInvocationTargetException(final InvocationTargetException ex) {
		checkMethodParameterNotNull("ex", ex);
		logger.throwing("zhu.framework.util.MyUtility", "rethrowInvocationTargetException", ex);
		//
		final Throwable cause = ex.getCause();
		if (cause instanceof RuntimeException) {
			throw (RuntimeException) cause;
		} else if (cause instanceof Error) {
			throw (Error) cause;
		} else {
			final UncheckedInvocationTargetException e = new UncheckedInvocationTargetException(ex.getMessage(), cause);
			throw e;
		}
	}

	@Inject
	private static Logger logger;
}
